#pragma once

#include <memory>
#include <string>
#include <string_view>

#include "coin2/exchange/base/api_base/fix/fix_trade_client_base.h"
#include "coin2/exchange/base/api_base/fix/fix_trade_client_interface.h"
#include "coin2/exchange/base/market/market_exchange_api.h"
#include "coin2/exchange/deribit_base/api_order/fix/fix_message_builder2.h"
#include "coin2/exchange/order/deribit.pb.h"
#include "naviasraw/fix/fix_sequencer.h"
#include "naviasraw/tcp/client/lws_client.h"
#include "naviasync/ipc/asio_forwarder.h"
#include "naviasync/ipc/coin_reactor.h"

namespace coin2::exchange::deribit::api_order::fix {

using namespace ::naviasraw::fix;

namespace resp_hdr {
#define FIX_PARSER_HDR_TAG_TRAITS(TMPL) TMPL(tag::common::MsgType)
DEF_RESP_PARSER(HdrParser, FIX_PARSER_HDR_TAG_TRAITS);
}  // namespace resp_hdr

namespace resp_seq_reset {
#define FIX_PARSER_SEQ_RESET_TAG_TRAITS(TMPL) TMPL(tag::common::NewSeqNo, int64_t)
DEF_RESP_PARSER(SeqResetParser, FIX_PARSER_SEQ_RESET_TAG_TRAITS);
}  // namespace resp_seq_reset

enum MsgType : char {
  kLogon = 'A',
  kHeartbeat = '0',
  kTestRequest = '1',
  kResendRequest = '2',
  kSequenceReset = '4',
  kLogout = '5'
};

class FixMsgSlicer : public ::naviasraw::tcp::client::MessageSlicerBase<FixMsgSlicer> {
  static constexpr auto kSOH = SOH[0];

 public:
  using Base = ::naviasraw::tcp::client::MessageSlicerBase<FixMsgSlicer>;
  using LogonCb = std::function<void(bool is_logon)>;
  using SequencResetCb = std::function<void(int64_t new_seq_no)>;
  using AppMessageCb = std::function<
      void(std::string_view msg_type, const base::order::BaseParser::RawMessage& app_msg)>;

  FixMsgSlicer(
      const MarketExchangeApi& mea,
      std::string_view ver_str,
      const LogonCb& logon_cb,
      const SequencResetCb& seq_reset_cb,
      const AppMessageCb& app_msg_cb)
      : Base(
            std::string_view("8=").size() + ver_str.size() + sizeof(kSOH) +
            std::string_view("9=00000000" SOH "35=XXXX" SOH).size()),
        mea_(mea),
        logon_cb_(logon_cb),
        seq_reset_cb_(seq_reset_cb),
        app_msg_cb_(app_msg_cb) {}

  void ConsumeBuffer(const char* buf, size_t buf_len, int64_t ts) {
    ts_ = ts;
    Base::ConsumeBuffer(buf, buf_len);
  }

  int RetrieveNumSkippedMsg() {
    auto rtn = num_skipped_msg_;
    num_skipped_msg_ = 0;
    return rtn;
  }

 protected:
  friend Base;

  const MarketExchangeApi& mea_;
  LogonCb logon_cb_;
  SequencResetCb seq_reset_cb_;
  AppMessageCb app_msg_cb_;
  resp_hdr::HdrParser<> hdr_parser_;
  resp_seq_reset::SeqResetParser<> seq_reset_parser_;
  int num_skipped_msg_ = 0;

  void DoReset() {
    this->Base::DoReset();
    hdr_parser_.Reset();
    seq_reset_parser_.Reset();
    num_skipped_msg_ = 0;
  }

  size_t DoParseHeader(const char* hdr_buf, bool& skip) {
    hdr_parser_.Reset();
    hdr_parser_.Parse(std::string_view(hdr_buf, Base::header_size_));
    std::stringstream oss;
    LOG(INFO) << "## Recv:" << hdr_parser_.DumpParsedTags(oss).str();
    is_seq_reset_ = false;
    auto msg_type_value = hdr_parser_.GetVal(tag::common::MsgType());
    msg_type_offset_ = msg_type_value.data() - hdr_buf;
    msg_type_len_ = msg_type_value.size();
    auto msg_type = (msg_type_value.size() == 1) ? msg_type_value[0] : '\0';
    switch (msg_type) {
      case '\0':
      case MsgType::kHeartbeat:
      case MsgType::kTestRequest:
        ++num_skipped_msg_;
        Base::skip_ = true;
        break;
      case MsgType::kResendRequest:
      case MsgType::kLogon:
        logon_cb_(true);
        ++num_skipped_msg_;
        Base::skip_ = true;
        break;
      case MsgType::kLogout:
        logon_cb_(false);
        ++num_skipped_msg_;
        Base::skip_ = true;
        break;
      case MsgType::kSequenceReset:
        is_seq_reset_ = true;
      default:
        break;
    }
    return hdr_parser_.hdr_length() + hdr_parser_.body_length() +
           std::string_view("10=000" SOH).size() - Base::header_size_;
  }

  void DoProcessMessage(const char* msg_buf, size_t msg_size) {
    if (__glibc_unlikely(is_seq_reset_)) {
      seq_reset_parser_.Reset();
      seq_reset_parser_.Parse(std::string_view(msg_buf, msg_size));
      DCHECK(seq_reset_parser_.IsPresented(tag::common::NewSeqNo()))
          << "No NewSeqNo in SequenceReset message";
      seq_reset_cb_(seq_reset_parser_.GetVal(tag::common::NewSeqNo()));
      return;
    }
    base::order::BaseParser::RawMessage raw_msg;
    raw_msg.type = coin::proto::OrderSubTopicId::RAW_BUFFER;
    raw_msg.raw_buffer = std::string_view(msg_buf, msg_size);
    raw_msg.timestamp = ts_;
    app_msg_cb_(std::string_view(msg_buf + msg_type_offset_, msg_type_len_), raw_msg);
  }

 private:
  int64_t ts_ = -1;
  size_t msg_type_offset_ = -1, msg_type_len_ = 0;
  bool is_seq_reset_ = false;
};

using IFixTradeClient = coin2::exchange::base::api_base::fix::IFixTradeClient;
class FixTradeClient2 : public IFixTradeClient {
 public:
  using SubscriberCB = std::function<void(const FIX::Message&)>;
  using Processor = coin2::exchange::base::order::BaseOrderProcessor;
  using RawMessage = coin2::exchange::base::order::BaseOrderProcessor::RawMessage;
  using OrderExecution = coin2::exchange::base::order::OrderExecution;
  using Executor = boost::asio::io_context;
  using ExecutorGuard = boost::asio::executor_work_guard<boost::asio::io_context::executor_type>;
  using FixMessageBuilder = coin2::exchange::deribit::api_order::fix::FixMessageBuilder2;
  using MarketExchangeApi = coin2::exchange::base::market::MarketExchangeApi;
  using GetProductHolderFunc = IFixTradeClient::GetProductHolderFunc;
  using LiveAsyncExecutor = base::executor::LiveAsyncExecutor;
  using CoinReactor =
      naviasync::ipc::CoinReactor<naviasync::ipc::AsioReactorSubscribeMode::kIoCtxOnly>;
  using SequencerMixin = naviasraw::fix::
      FixSequencerMixin<naviasraw::tcp::client::LwsClient, naviasync::ipc::AsioForwarder>;
  using StreamClient =
      naviasraw::tcp::client::LwsStreamClient<SequencerMixin, CoinReactor, CoinReactor::ResMgr>;
  using ForwarderGroup = naviasraw::ForwarderGroup<StreamClient::EmbeddedForwarder>;
  using FixTradeClientContext = order::DeribitFixTradeClientContext;

  struct Context {
    FixTradeClientContext trade_client_ctx;
    std::string sender_comp_id;
    std::string access_key;
    std::string secret_key;
  };

  explicit FixTradeClient2(std::string_view, const MarketExchangeApi&);
  void Start() override;
  void Stop() override;
  void OnTimer(const boost::system::error_code& ec);
  bool IsAvailable() const override;
  void SetExecutor(Executor*) override;
  void SetMainReactor(base::executor::CoinReactor&) override;
  void SetProcessor(Processor*) override;
  void SetGetProductHolder(GetProductHolderFunc get_product_holder) override {
    get_product_holder_ = get_product_holder;
  }
  void AddSubscriber(SubscriberCB) override;
  void PlaceOrder(const OrderExecution&) override;
  void CancelOrder(const OrderExecution&) override;
  void QueryOrder(const OrderExecution&) override{};

  auto GetProductHolder() const { return get_product_holder_; }
  auto GetContractValue(
      const std::string& native_product,
      int64_t ts,
      const MarketExchangeApi& mea,
      GetProductHolderFunc get_product_holder) -> double;
  void SetContractValue(const std::string& native_product, double val);
  bool ParseAppMessage(std::string_view msg_type, std::string_view message);

 private:
  static constexpr char kTradeClientConfig[] = "data/coin2/order/fix/deribit/tradeclient2.json";
  static constexpr char kBeginString[] = "FIX.4.4";
  static constexpr char kTargetCompId[] = "DERIBITSERVER";
  const MarketExchangeApi mea_;
  Context ctx_;
  std::unique_ptr<FixMessageBuilder2> msg_builder_;
  GetProductHolderFunc get_product_holder_;
  std::unique_ptr<boost::asio::steady_timer> timer_ = {};
  std::unordered_map<std::string, double> contract_value_map_;
  Executor* executor_ = nullptr;
  base::executor::CoinReactor* main_reactor_ = nullptr;
  Processor* processor_ = nullptr;
  std::unique_ptr<StreamClient> stream_client_;
  std::unique_ptr<ForwarderGroup> forwarder_grp_;
  boost::asio::io_context og_raw_ioc_;
  boost::asio::executor_work_guard<boost::asio::io_context::executor_type> og_raw_ioc_guard_ =
      boost::asio::make_work_guard(og_raw_ioc_);
  FixMsgSlicer fix_msg_slicer_;

  bool is_inited_ = false;
  bool is_logon_ = false;
  int64_t next_recv_seq_no_ = 1;

  void EstablishSession();
};

}  // namespace coin2::exchange::deribit::api_order::fix
